#include "camera.h"

CCamera::CCamera():

    m_fFOVY(Constants::CAMERA_FIELD_OF_VIEW),
    m_fAspect(Constants::CAMERA_ASPECT_RATIO),
    m_fZN(Constants::CAMERA_Z_NEAR_PLANE),
    m_fZF(Constants::CAMERA_Z_FAR_PLANE),

    m_vecPosition(Constants::CAMERA_POSITION),
    m_vecLookAt(Constants::CAMERA_LOOKAT),
    m_vecUp(Constants::CAMERA_UP),

    m_pDevice(nullptr)
{
    /**
     * Constructor, ensure that all member attributes are initialised to the 
     * default values, Also ensure there are no dangling pointers.
     */
}

CCamera* CCamera::getInstance() 
{
    /**
     * Obtains the single static instance of the camera. This attempts to
     * create an instance of this object if it doesn't exist. If it already
     * exists, then return the address of this instance. This uses the
     * singleton methodology.
     * 
     * @static
     * @return CLightContainer* A pointer to the static singleton of the 
     *                          container.
     */
    static CCamera instance;
    return &instance;
}
   
void CCamera::setup(LPDIRECT3DDEVICE9 device) 
{
    /**
     * Setup the camera class by giving it a pointer to the device that it will 
     * control the camera of.
     * 
     * @param LPDIRECT3DDEVICE9 device The device to edit.
     */
    m_pDevice = device;
}
    
void CCamera::setPosition(const D3DXVECTOR3 &position) 
{
    /**
     * Set the position of the camera using a vector.
     * 
     * @param D3DXVECTOR3 position The vector to take the new camera position
     * from.
     */
    m_vecPosition = position;
}

void CCamera::setPosition(float x, float y, float z)
{
    /**
     * Set the position of the camera using three coordinate values. creates 
     * a vector and initialised it with the parameters.
     * 
     * @param float x The new x coordinate of the camera.
     * @param float y The new y coordinate of the camera.
     * @param float z The new z coordinate of the camera.
     */ 
    D3DXVECTOR3 position(x, y, z);
    m_vecPosition = position;
}

D3DXVECTOR3 CCamera::getPosition() const 
{
    /**
     * @return D3DXVECTOR3 The camera position as a vector
     */ 
    return m_vecPosition;
}

void CCamera::getPosition(float &x, float &y, float &z) const 
{
    /**
     * Obtains the position of the camera.
     *
     * @param float &x A reference to the variable to hold the cameras x 
     *    coordinate
     * @param float &y A reference to the variable to hold the cameras y 
     *    coordinate
     * @param float &z A reference to the variable to hold the cameras z 
     *    coordinate
     */
    x = m_vecPosition.x;
    y = m_vecPosition.y;
    z = m_vecPosition.z;
}

void CCamera::setLookAt(const D3DXVECTOR3 &lookat) 
{
    /**
     * Set the position the camera is looking at.
     * 
     * @param D3DXVECTOR3 position The vector to take the new camera lookat
     * from.
     */
    m_vecLookAt = lookat;
}

void CCamera::setLookAt(float x, float y, float z) 
{
    /**
     * Set the lookat of the camera using three coordinate values creates 
     * a vector and initialised it with the parameters.
     * 
     * @param float x The new x coordinate of the camera.
     * @param float y The new y coordinate of the camera.
     * @param float z The new z coordinate of the camera.
     */ 
    D3DXVECTOR3 lookat(x,y,z);
    m_vecLookAt = lookat;
}

D3DXVECTOR3 CCamera::getLookAt() const 
{
    /**
     * @return D3DXVECTOR3 The camera lookat as a vector.
     */ 
    return m_vecLookAt;
}

void CCamera::getLookAt(float &x, float &y, float &z) const 
{
    /**
     * Obtains the lookat of the camera.
     *
     * @param float &x A reference to the variable to hold the cameras lookat x
     * coordinate
     * @param float &y A reference to the variable to hold the cameras lookat y 
     * coordinate
     * @param float &z A reference to the variable to hold the cameras lookat z 
     * coordinate
     */
    x = m_vecLookAt.x;
    y = m_vecLookAt.y;
    z = m_vecLookAt.z;
}





void CCamera::setUp(const D3DXVECTOR3 &up)
{
    /**
     * Set the Camera up.
     * 
     * @param D3DXVECTOR3 position The vector to take the new camera up from.
     */
    m_vecUp = up;
}

void CCamera::setUp(float x, float y, float z) 
{
    /**
     * Set the up value of the camera using three coordinate values.  creates 
     * a vector and initialised it with the parameters. 
     * 
     * @param float x The new x coordinate of the camera.
     * @param float y The new y coordinate of the camera.
     * @param float z The new z coordinate of the camera.
     */ 
    D3DXVECTOR3 up(x,y,z);
    m_vecUp = up;
}

D3DXVECTOR3 CCamera::getUp() const 
{
    /**
     * @return D3DXVECTOR3 The camera up as a vector.
     */ 
    return m_vecUp;
}

void CCamera::getUp(float &x, float &y, float &z) const 
{
    /**
     * Obtains the up of the light.
     *
     * @param float &x A reference to the variable to hold the cameras up x
     * coordinate
     * @param float &y A reference to the variable to hold the cameras up y 
     * coordinate
     * @param float &z A reference to the variable to hold the cameras up z 
     * coordinate
     */

    x = m_vecUp.x;
    y = m_vecUp.y;
    z = m_vecUp.z;
}

void CCamera::setFOVY(float angle) 
{
    /**
     * Set the Camera Field of view y.
     * 
     * @param float angle The field of view in radians.
     */
    m_fFOVY = angle;
}
    
float CCamera::getFOVY() const 
{
    /**
     * @return float The camera field of view in radians.
     */ 
    return m_fFOVY;
}

void CCamera::setAspect(float ratio) 
{
    /**
     * Set the aspect ration
     * 
     * View space width divided by height.
     *
     * @param float ratio The aspect ratio.
     */
    m_fAspect = ratio;
}

float CCamera::getAspect() const 
{
    /**
     * @return float The camera aspect ration.
     */ 
    return m_fAspect;
}

void CCamera::setZNearPlane(float distance) 
{
    /**
     * Set the Z near plane. 
     * 
     * @param float distance The distance from the camera to the near plane.
     */
    m_fZN = distance;
}

float CCamera::getZNearPlane() const 
{
    /**
     * @return float The camera Z near plane.
     */ 
    return m_fZN;
}

void CCamera::setZFarPlane(float distance) 
{
    /**
     * Set the Z far plane. 
     * 
     * @param float distance The distance from the camera to the far plane.
     */
    m_fZF = distance;
}

float CCamera::getZFarPlane() const 
{
    /**
     * @return float The camera Z far plane.
     */ 
    return m_fZF;
}

void CCamera::applyViewMatrix()
{
    /**
     * Creates a new look at matrix in the left had coordinate system and 
     * sets the transform to the world on the device.
     */
    D3DXMatrixLookAtLH( &m_matView, &m_vecPosition, &m_vecLookAt, &m_vecUp); 
    m_pDevice -> SetTransform(D3DTS_VIEW, &m_matView);
}

void CCamera::applyProjectionMatrix()
{
    /**
     * Creates a perspective matrix and sets the transform to the world on the 
     * device.
     */

    //D3DXMatrixOrthoLH
    D3DXMatrixPerspectiveFovLH(&m_matProjection, m_fFOVY, m_fAspect, m_fZN, 
                               m_fZF);
    m_pDevice -> SetTransform(D3DTS_PROJECTION, &m_matProjection);
}

void CCamera::applyOrthographicMatrix(HWND hWnd)
{
    /** 
     * Applies the orthographic matrix to the camera. This obtains the matrix
     * needed to project a 2D image onto the screen.
     *
     * @param HWND hWnd THe window handle
     */
    RECT rect;
    int nScreenWidth(Constants::WINDOW_WIDTH);
    int nScreenHeight(Constants::WINDOW_HEIGHT);

    if(GetWindowRect(hWnd, &rect))
    {
        nScreenWidth = rect.right - rect.left;
        nScreenHeight = rect.bottom - rect.top;
    }

    D3DXMatrixOrthoLH(&m_matOrtho, 
                      (float)nScreenWidth,
                      (float)nScreenHeight,
                      0.0F, 
                      1.0F);
}

D3DXMATRIX CCamera::getViewMatrix()
{
    /**
     * @return D3DXMATRIX The view matrix
     */
    return m_matView;
}

D3DXMATRIX CCamera::getProjectionMatrix()
{
    /**
     * @return D3DXMATRIX The projection matrix
     */
    return m_matProjection;
}

void CCamera::setup3D()
{
    /**
     * Sets the camera up for 3D drawing. This changes the camera to perspective
     * projection.
     */
    
    // Enable the Z buffer, since we're dealing with 3D geometry.
    m_pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

    // Enable the light
    m_pDevice->SetRenderState(D3DRS_LIGHTING, D3DZB_TRUE);

    // 3D is a view / projection matrix
    m_pDevice->SetTransform(D3DTS_VIEW, &m_matView);
    m_pDevice->SetTransform(D3DTS_PROJECTION, &m_matProjection);
}

void CCamera::setup2D()
{
    /**
     * Sets the camera up for 2D drawing. This changes the camera to 
     * orthographic projection.
     */

    // Enable the Z buffer, since we're dealing with 3D geometry.
    m_pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);

    // Enable the light
    m_pDevice->SetRenderState(D3DRS_LIGHTING, D3DZB_FALSE);

    m_pDevice->SetTransform(D3DTS_VIEW, &m_matView);
    m_pDevice->SetTransform(D3DTS_PROJECTION, &m_matOrtho);
}